/*
    This source file is part of GeNa project.
    Copyright © GeNa project 2008
    For the latest info, see http://code.google.com/p/genaproject/ or  http://lumpyproject.forums-rpg.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Foobar is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. */

#include <cmath>
#include <cfloat>
#include <climits>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/casts.hpp>

#include "editor_window.h"
#include "unique_name_generator.h"
#include "application.h"
#include "map_manager.h"
#include "model.h"
#include "axis.h"
#include "action.h"
#include "property_manager.h"

#include <boost/serialization/list.hpp>
#include "serialized_data.h"
#include "map_serialization.h"


#include <dae.h>
#include <dom/domCOLLADA.h>

using namespace boost::lambda;

//TODO Do not forget selection list when ereasing something

class MoveSelection : public Action, public wxEvtHandler
{
private:
    math::vector3q center;
    math::vector3q last;
    EditorWindow* w;
    bool end;

    std::list<int> objs;

protected:
    void restoreObjectsState()
    {
        for( std::list<int>::iterator iter = objs.begin(); iter != objs.end(); iter++)
        {
            int id = *iter;
            Model* m = dynamic_cast<Model*>( Object::getObject(id));

            if( isUndo)
                m->position -= last;
            else
                m->position += last;
        }

        Action::restoreObjectsState();
    }
public:
    MoveSelection(EditorWindow* wnd)
    {
        w = wnd;
        end = false;

        wxPoint p = wnd->ScreenToClient(wxGetMousePosition());
        center = w->getPosition(p.x,p.y);

        Map* map = mapManagerInstance.current;
        for( std::list<Model*>::iterator iter = map->selectedModels.begin(); iter != map->selectedModels.end(); iter++)
           objs.push_front( (*iter)->getId());

        wnd->PushEventHandler(this);
    }

    DECLARE_EVENT_TABLE()

    void onMouseEvent(wxMouseEvent &evt)
    {
        math::vector3q newPos = w->getAligned(w->getPosition(evt.GetX(), evt.GetY()) - center);
        math::vector3q t = w->to3dVector(newPos - last);
        last = newPos;

        for( std::list<int>::iterator iter = objs.begin(); iter != objs.end(); iter++)
        {
            int id = *iter;
            Model* m = dynamic_cast<Model*>( Object::getObject(id));
            m->position += t;
        }

        if( !evt.LeftIsDown())
        {
            w->PopEventHandler();
            end = true;

            if( (last.x == math::fixed(0)) && (last.y == math::fixed(0)))
            {
                //Nothing have been done
                actionManagerInstance.abortCurrentAction();
                return;
            }
            last = w->to3dVector(last);
            actionManagerInstance.endCurrentAction();
            return;
        }

        if( evt.RightIsDown()) //Avoid user selection
            return;

        evt.Skip(); //But allow to move view
    }

    ~MoveSelection()
    {
        if( !end)
        {
            for( std::list<int>::iterator iter = objs.begin(); iter != objs.end(); iter++)
            {
                int id = *iter;
                Model* m = dynamic_cast<Model*>( Object::getObject(id));
                m->position -= w->to3dVector(last);
            }
            w->PopEventHandler();
        }
    }

    std::string getDescription()
    {
        return "Move selected objects.";
    }
};

class RotateSelection : public Action, public wxEvtHandler
{
private:
    math::vector3q center;
    EditorWindow* w;
    bool end;

    std::list<int> objs;

public:
    RotateSelection(EditorWindow* wnd)
    {
        w = wnd;
        end = false;

        wxPoint p = wnd->ScreenToClient(wxGetMousePosition());
        center = w->getPosition(p.x,p.y);

        Map* map = mapManagerInstance.current;
        for( std::list<Model*>::iterator iter = map->selectedModels.begin(); iter != map->selectedModels.end(); iter++)
           objs.push_front( (*iter)->getId());

        wnd->PushEventHandler(this);
    }

    DECLARE_EVENT_TABLE()

    void onMouseEvent(wxMouseEvent &evt)
    {
        math::vector3f vec = w->to3dVector(w->getPosition(evt.GetX(), evt.GetY()) - center) ;
        math::quaternionf quat = getTransformation((math::vector3f)w->to3dVector(math::vector3q(0,1,0)), vec);

        if( quat.z == 1.0f) //Unexpected result, because vec | math::vector3q(0,1,0) = 0
        {
            vec = w->to3dVector(math::vector3q(0,1,0)) ^ w->to3dVector(math::vector3q(1,0,0));
            quat.x = vec.x;
            quat.y = vec.y;
            quat.z = vec.z;
            quat.w = 0;
        }

        restoreObjectsState();
        actionList.clear();

        for( std::list<int>::iterator iter = objs.begin(); iter != objs.end(); iter++)
        {
            int id = *iter;
            Model* m = dynamic_cast<Model*>( Object::getObject(id));
            saveObjectMember(&Model::orientation, id);
            m->orientation = quat*m->orientation;
        }

        if( evt.LeftIsDown())
        {
            w->PopEventHandler();
            end = true;
            actionManagerInstance.endCurrentAction();
            return;
        }

    }

    ~RotateSelection()
    {
        if( !end)
        {
            restoreObjectsState();
            w->PopEventHandler();
        }

    }

    std::string getDescription()
    {
        return "Rotate selected objects.";
    }
};

class NewModelAction : public Action
{
private:
    Map* map;
    std::list<int> models;

protected:
    void restoreObjectsState()
    {
        if( isUndo)
        {
            for( std::list<int>::iterator iter = models.begin(); iter != models.end(); iter++)
            {
                int id = *iter;
                map->selectedModels.remove_if(boost::lambda::bind(&Model::getId,_1) == id);
                map->models.remove_if(boost::lambda::bind(&Model::getId,_1) == id);
            }
        }

        Action::restoreObjectsState();

        if( !isUndo)
        {
            for( std::list<int>::iterator iter = models.begin(); iter != models.end(); iter++)
            {
                int id = *iter;
                Model* m = dynamic_cast<Model*>( Object::getObject(id));
                map->models.push_back( m);

                map->unselectAll();
                map->selectedModels.push_back( m);
            }
        }

    }
public:
    NewModelAction(std::string name, math::vector3q pos)
    {
        map = mapManagerInstance.current;

        Model* model = new Model;
        models.push_front(model->getId());
        model->name = name;
        model->position = pos;
        model->load(mapManagerInstance.current);

        model->select();
        map->unselectAll();
        map->models.push_back(model);
        map->selectedModels.push_back(model);

        addNewObject(model);
    }

    NewModelAction( std::list<Model*> _models)
    {
        map = mapManagerInstance.current;
        for( std::list<Model*>::iterator iter = _models.begin(); iter != _models.end(); iter++)
        {
            models.push_back( (*iter)->getId());
            addNewObject( *iter);
        }
    }

    void registered()
    {
        actionManagerInstance.endCurrentAction();
    }

    std::string getDescription()
    {
        return "Add a new model.";
    }
};

class DeleteModelAction : public Action
{
private:
    std::string name;
    math::vector3q pos;
    Map* map;

    std::list<int> models;

protected:
    void restoreObjectsState()
    {
        if( !isUndo)
        {
            for( std::list<int>::iterator iter = models.begin(); iter != models.end(); iter++)
            {
                int id = *iter;
                map->models.remove_if(boost::lambda::bind(&Model::getId,_1) == id);
                map->selectedModels.remove_if(boost::lambda::bind(&Model::getId,_1) == id);
            }
        }

        Action::restoreObjectsState();

        if( isUndo)
        {
            map->unselectAll();

            for( std::list<int>::iterator iter = models.begin(); iter != models.end(); iter++)
            {
                int id = *iter;
                Model* m = dynamic_cast<Model*>( Object::getObject(id));
                m->select();
                map->models.push_back( m);
                map->selectedModels.push_back( m);
            }
        }

    }
public:
    DeleteModelAction()
    {
        map = mapManagerInstance.current;
        for( std::list<Model*>::iterator iter = map->selectedModels.begin(); iter != map->selectedModels.end(); iter++)
           models.push_front( (*iter)->getId());
    }

    void registered()
    {
        for( std::list<Model*>::iterator iter = map->selectedModels.begin(); iter != map->selectedModels.end(); iter++)
        {
            map->models.remove_if(boost::lambda::bind(&Model::getId,_1) == (*iter)->getId());
            removeObject(*iter);
        }
        map->selectedModels.clear();
        actionManagerInstance.endCurrentAction();
    }

    std::string getDescription()
    {
        return "Remove selected objects.";
    }
};


BEGIN_EVENT_TABLE(MoveSelection, wxEvtHandler)
	EVT_MOUSE_EVENTS(MoveSelection::onMouseEvent)
END_EVENT_TABLE()

BEGIN_EVENT_TABLE(RotateSelection, wxEvtHandler)
	EVT_MOUSE_EVENTS(RotateSelection::onMouseEvent)
END_EVENT_TABLE()


BEGIN_EVENT_TABLE(EditorWindow, GLWindow)
	EVT_MOUSE_EVENTS(EditorWindow::onMouseEvent)
	EVT_KEY_DOWN(EditorWindow::onKeyDown)
	EVT_MENU(idMenuModel,EditorWindow::onMenuEvent)
END_EVENT_TABLE()


math::vector3q m_3dtest(0,0,0);

float round( float f, float base)
{
    f /= base;
    double ipart;

    if( f < 0)
    {
        if( modf( f, &ipart) < -0.5f)
            ipart -= 1.0f;
    }
    else
    {
        if( modf( f, &ipart) > 0.5f)
            ipart += 1.0f;
    }
    return ipart*base;
}


void EditorWindow::init()
{


	// Get wx control window size
	int width;
	int height;
	GetSize(&width, &height);

    updateView();
    m_axis = new Axis(m_viewMat);

    isInitialized = true;
}

void EditorWindow::update(int interval)
{
    SetCurrent();

    int width;
    int height;
    GetSize(&width, &height);

    glViewport(0,0, width, height);

    glDisable(GL_DEPTH_TEST);
    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT);

    updateGrid();
    updateView();
    glMatrixMode(GL_PROJECTION);
    glLoadTransposeMatrixf(m_projMat.l);
    glMatrixMode(GL_MODELVIEW);
    glLoadTransposeMatrixf(m_viewMat.l);

    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    mapManagerInstance.current->draw(true);
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

    m_axis->draw();

    SwapBuffers();
}

void EditorWindow::updateGrid()
{

    int width, height;
    GetSize(&width, &height);

    math::Vector3<float> m_bottomleft = -m_center + math::vector3q(-(float)width/(2.0f*m_zoom),-(float)height/(2.0f*m_zoom), 0);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glBegin(GL_LINES);
    glColor3f(0.6,0.6,0.6);

    //Vertical lines
    {
        int n = (int)(width/(m_zoom* m_res)) + 2;

        float step = 2*m_res*m_zoom/width; //OpenGL framebuffer take value in range [-1,1]

        float first = 2*( m_res* (int)(m_bottomleft.x/m_res - 1)- m_bottomleft.x) /(float)width*m_zoom - 1;

        for( int i=0; i<n;i++)
        {
            glVertex3f( i*step + first, -1, 1);
            glVertex3f( i*step + first, 1, 1);
        }
    }

    //Horizontal lines
    {
        int n = (int)(height/(m_zoom* m_res)) + 3;

        float step = 2*m_res*m_zoom/height; //OpenGL framebuffer take value in range [-1,1]
        float first = 2*( m_res* (int)(m_bottomleft.y/m_res - 1)- m_bottomleft.y ) /(float)height*m_zoom - 1;

        for( int i=0; i<n;i++)
        {
            glVertex3f( 1,i*step + first, 1);
            glVertex3f( -1, i*step + first, 1);
        }
    }


    //Same but with a 64-resolution

    glColor3f(0.0,0.0,0.0);
    //Vertical lines
    {
        int n = (int)(width/(m_zoom* 64)) + 2;

        float step = 2*m_res2*m_zoom/width; //OpenGL framebuffer take value in range [-1,1]

        float first = 2*( m_res2* (int)(m_bottomleft.x/m_res2 - 1)- m_bottomleft.x) /(float)width*m_zoom - 1;

        for( int i=0; i<n;i++)
        {
            glVertex3f( i*step + first, -1, 1);
            glVertex3f( i*step + first, 1, 1);
        }
    }

    //Horizontal lines
    {
        int n = (int)(height/(m_zoom* 64)) + 3;

        float step = 2*m_res2*m_zoom/height; //OpenGL framebuffer take value in range [-1,1]
        float first = 2*( m_res2* (int)(m_bottomleft.y/m_res2 - 1)- m_bottomleft.y) /(float)height*m_zoom - 1;

        for( int i=0; i<n;i++)
        {
            glVertex3f( 1,i*step + first, 1);
            glVertex3f( -1, i*step + first, 1);
        }
    }

    glEnd();
}

math::matrix4f BuildScaledOrthoMatrix(float left, float right, float bottom, float top, float _near, float far)
{
  float invw = 1 / (right - left);
  float invh = 1 / (top - bottom);
  float invd = 1 / (far - _near);

  math::matrix4f proj = math::matrix4f::Null;
  proj[0][0] = 2 * invw;
  proj[0][3] = -(right + left) * invw;
  proj[1][1] = 2 * invh;
  proj[1][3] = -(top + bottom) * invh;
  proj[2][2] = -2 * invd;
  proj[2][3] = -(far + _near) * invd;
  proj[3][3] = 1;

  return proj;
}

void EditorWindow::updateView()
{

    switch(m_view)
    {
        case 0:
            m_center.x = m_3dCenter.x;
            m_center.y = m_3dCenter.y;
            m_viewMat = math::matrix4f(1,0,0,m_center.x,  0,1,0,m_center.y,  0,0,1,0,  0,0,0,1);
            break;
        case 1:
            m_center.x = m_3dCenter.x;
            m_center.y = m_3dCenter.z;
            m_viewMat = math::matrix4f(1,0,0,m_center.x,  0,0,1,m_center.y,  0,1,0,0,  0,0,0,1);
            break;
        case 2:
            m_center.x = m_3dCenter.y;
            m_center.y = m_3dCenter.z;
            m_viewMat = math::matrix4f(0,1,0,m_center.x,  0,0,1,m_center.y,  1,0,0,0,  0,0,0,1);
            break;
    }

    int width, height;
    GetSize(&width, &height);

    m_projMat = BuildScaledOrthoMatrix(-(float)width/(2.0f*m_zoom),(float)width/(2.0f*m_zoom),-(float)height/(2.0f*m_zoom),(float)height/(2.0f*m_zoom),LONG_MIN,LONG_MAX);
    //Update orthographic view
//    mCamera->setCustomProjectionMatrix(true,
//        Ogre::Matrix4( 1000*(2.0f*m_zoom)/(float)width, 0,                    0, 1000*m_center.x*(2.0f*m_zoom)/(float)width,
//                 0,                   1000*(2.0f*m_zoom)/(float)height, 0, 1000*m_center.y*(2.0f*m_zoom)/(float)height,
//                 0,                   0,                    0.0001, 1000,
//                 0,                   0,                    0, 1));
//    mCamera->setProjectionType( Ogre::PT_ORTHOGRAPHIC);
//    mCamera->setOrthoWindow(width, height);
}

math::vector3q EditorWindow::getPosition(int x, int y)
{
    int width, height;
    GetSize(&width, &height);

    return math::vector3q(math::fixed(((float)x - width/2.0f)/m_zoom) - m_center.x, math::fixed(((float)(-y) + height/2.0f)/m_zoom) - m_center.y, 0 );
}

math::vector3q EditorWindow::getAlignedPosition( int x, int y)
{
    math::vector3q p = getPosition(x, y);
    p.x = round(p.x, m_res);
    p.y = round(p.y, m_res);
    return p;
}

math::vector3q EditorWindow::getVector(int x, int y)
{
    int width, height;
    GetSize(&width, &height);

    return math::vector3q(((float)x)/m_zoom, -((float)y)/m_zoom,0);
}

math::vector3q EditorWindow::getAlignedVector( int x, int y)
{
    math::vector3q p = getVector(x, y);
    p.x = round(p.x, m_res);
    p.y = round(p.y, m_res);
    return p;
}

math::vector3q EditorWindow::getAligned( math::vector3q p)
{
    p.x = round(p.x, m_res);
    p.y = round(p.y, m_res);
    return p;
}

math::vector3q EditorWindow::to3dVector(math::vector3q v)
{
    math::vector3q r = m_3dCenter;

    switch( m_view)
    {
        case 0:
            r.x = v.x;
            r.y = v.y;
            break;
        case 1:
            r.x = v.x;
            r.z = v.y;
            break;
        case 2:
            r.y = v.x;
            r.z = v.y;
            break;
    }
    return r;
}

void EditorWindow::onMouseEvent(wxMouseEvent &event)
{
    bool update = false;

    if( event.Entering())
    {
        if(appInstance->m_frame->IsActive())
            SetFocus();
        return;
    }

    if( event.LeftDown())
    {

        actionManagerInstance.setCurrentAction(new MoveSelection(this));

        return;
    }

    //Remember mouse position when dragging using middle button, or when calling popup menu with left click
    if( event.MiddleDown() || event.RightDown())
    {
        m_mouseX = event.GetX();
        m_mouseY = event.GetY();

        if( event.RightDown())
        {
            math::vector3a v;
            switch(m_view)
            {
                case 0:
                    v.z = 1.0;
                    break;
                case 1:
                    v.y = 1.0;
                    break;
                case 2:
                    v.x = 1.0;
                    break;
            }


            if( wxGetKeyState(WXK_SHIFT))
                mapManagerInstance.current->unselect(to3dVector(getPosition(m_mouseX,m_mouseY)),v);
            else
                mapManagerInstance.current->select(to3dVector(getPosition(m_mouseX,m_mouseY)),v);
        }

    }

    if( event.MiddleIsDown())
    {
        m_center += getVector(event.GetX()-m_mouseX, event.GetY()-m_mouseY);

        switch( m_view)
        {
            case 0:
                m_3dCenter.x = m_center.x;
                m_3dCenter.y = m_center.y;
                break;
            case 1:
                m_3dCenter.x = m_center.x;
                m_3dCenter.z = m_center.y;
                break;
            case 2:
                m_3dCenter.y = m_center.x;
                m_3dCenter.z = m_center.y;
                break;
        }
        m_mouseX = event.GetX();
        m_mouseY = event.GetY();

        update = true;
    }

    if(event.GetWheelRotation())
    {
        m_zoom *= 1 + event.GetWheelRotation()/(float)(2*event.GetWheelDelta());
        update = true;
    }

    if( update)
    {
        updateView();
    }

    math::vector3q p = getAlignedPosition(event.GetX(), event.GetY());
    wxString msg;
    msg << wxString(wxT("( ")) << (int)p.x << wxT(" , ") << (int)p.y << wxT(" ) -> (") << (int)p.x/64.0f << wxT(" , ") << (int)p.y/64.0f << wxT(" )");
    appInstance->m_frame->SetStatusText( msg,0);
}

void EditorWindow::onKeyDown(wxKeyEvent &event)
{
    if( event.GetKeyCode() == WXK_TAB)
    {
        if( event.ShiftDown())
            m_view = (m_view-1) % 3;
        else
            m_view = (m_view+1) % 3;
        updateView();
        return;
    }


    if( event.GetKeyCode() == WXK_SPACE)
    {
        m_mouseX = event.GetX();
        m_mouseY = event.GetY();

        PopupMenu(popupMenu);
        return;
    }

    if( event.GetKeyCode() == WXK_BACK || event.GetKeyCode() == WXK_DELETE)
    {
        if( event.ShiftDown())
            copy();

        if( mapManagerInstance.current->selectedModels.size())
            actionManagerInstance.setCurrentAction(new DeleteModelAction);

        return;
    }

    if( event.GetKeyCode() == 'A')
    {
        mapManagerInstance.current->unselectAll();
        return;
    }

    if( event.GetKeyCode() == 'C')
    {
        if( event.ControlDown())
        {
            copy();
            return;
        }
    }

    if( event.GetKeyCode() == 'V')
    {
        if( event.ControlDown())
        {
            paste();
            return;
        }
    }

    if( event.GetKeyCode() == 'R')
    {
        actionManagerInstance.setCurrentAction(new RotateSelection(this));
        return;
    }

    if( event.GetKeyCode() == WXK_INSERT)
    {
        if( event.ControlDown())
        {
            copy();
            return;
        }


        if( event.ShiftDown())
        {
            paste();
            return;
        }
    }
}

void EditorWindow::onMenuEvent(wxCommandEvent & event)
{
    switch( event.GetId())
    {
        case idMenuModel:
        {
            wxTextEntryDialog* dlg = new wxTextEntryDialog(this, _T("Nom du modele : "), _T("Choisissez un modele ..."), _T(""), wxOK | wxCANCEL);

            if( dlg->ShowModal() != wxID_OK)
                return;
            if( dlg->GetValue() == _T(""))
                return;

            actionManagerInstance.setCurrentAction(new NewModelAction(std::string(dlg->GetValue().mb_str()), to3dVector(getAlignedPosition(m_mouseX, m_mouseY))));

        }
        break;
    }
}

void EditorWindow::copy()
{
    SerializedData* data = new SerializedData(wxString(_T("SaphiraEditorSelectedData")));

    wxPoint pt = ScreenToClient(wxGetMousePosition());
    *data << mapManagerInstance.current->selectedModels << to3dVector(getPosition(pt.x, pt.y)) ;

    if( wxTheClipboard->Open())
    {
        wxTheClipboard->SetData(data);
        wxTheClipboard->Close();
    }
}

void EditorWindow::paste()
{
    if( wxTheClipboard->Open())
    {
        if( wxTheClipboard->IsSupported(wxDataFormat(wxString(_T("SaphiraEditorSelectedData")))))
        {
            SerializedData data(wxString(_T("SaphiraEditorSelectedData")));
            wxTheClipboard->GetData(data);
            wxTheClipboard->Close();

            Map* map = mapManagerInstance.current;
            map->unselectAll();

            math::vector3q delta;
            data >> mapManagerInstance.current->selectedModels >> delta;

            wxPoint pt = ScreenToClient(wxGetMousePosition());
            delta = getAligned(to3dVector(getPosition(pt.x, pt.y)) - delta);

            for(std::list<Model*>::iterator iter = map->selectedModels.begin(); iter != map->selectedModels.end(); iter++)
            {
                (*iter)->position += delta;
                map->models.push_back(*iter);
                (*iter)->load(map);
                (*iter)->select();
            }

            actionManagerInstance.setCurrentAction(new NewModelAction(map->selectedModels));
        }
    }
}

